#!/usr/bin/python
import Tkinter, tkFileDialog
import Image, ImageTk
import ImageFilter
import ImageDraw
import sys
import os
import signal
import math
import subprocess
import time

def _(s): return s  # TODO: i18n

app = Tkinter.Tk()
app.wm_title(_("CropGUI -- lossless cropping and rotation of jpeg files"))
app.wm_iconname(_("CropGUI"))

preview = Tkinter.Label(app)

tempframe = Tkinter.Frame(app)
do_crop = Tkinter.Button(tempframe, text="Save slice")
done_crop = Tkinter.Button(tempframe, text="Done cropping")
undo_crop = Tkinter.Button(tempframe, text="Undo crop")
redo_crop = Tkinter.Button(tempframe, text="Redo crop")
do_crop.grid(row=1,column=1,sticky="new",pady = 3,padx=3)
done_crop.grid(row=1,column=2,sticky="new",pady = 3)
undo_crop.grid(row=2,column=1,sticky="new",padx = 3)
redo_crop.grid(row=2,column=2,sticky="new")
info = Tkinter.Label(tempframe)
info.grid(row=1,rowspan=2,column=3,sticky="n",ipadx = 15)
tempframe.pack(side="top", expand=0)

preview.pack(side="bottom")


(   
    DRAG_NONE,
    DRAG_TL, DRAG_T, DRAG_TR,
    DRAG_L,  DRAG_C, DRAG_R,
    DRAG_BL, DRAG_B, DRAG_BR
) = range(10)

def clamp(value, low, high):
    if value < low: return low
    if high < value: return high
    return value

class DragManager(object):
    def __init__(self, w, inf=None):

        self.mainloop = True
        self.render_flag = 0
        self.l = w

        buttonList = [(do_crop,self.done),(done_crop,self.finish),(undo_crop, self.undocrop),(redo_crop,self.redocrop)]
        
        for (b,command) in buttonList:
            if b: b.configure(command=command)
        
        self.inf = inf
        w.bind("<Button-1>", self.start)
        w.bind("<Double-Button-1>", self.start)
        w.bind("<Button1-Motion>", self.motion)
        w.bind("<ButtonRelease-1>", self.end)
        dummy_image = Image.fromstring('RGB', (1,1), '\0\0\0')
        self.dummy_tkimage = ImageTk.PhotoImage(dummy_image)
        self.state = DRAG_NONE
        self.round = 1
        self.image = None
        w.configure(image=self.dummy_tkimage)
        self.v = Tkinter.IntVar(app)
        self.cropredolist = []
        self.cropinfo = []

    def get_w(self): return self.image.size[0]
    w = property(get_w)
    def get_h(self): return self.image.size[1]
    h = property(get_h)

    def set_image(self, image):
        if image is None:
            if hasattr(self, 'left'): del self.left
            if hasattr(self, 'right'): del self.right
            if hasattr(self, 'bottom'): del self.bottom
            if hasattr(self, 'blurred'): del self.blurred
            if hasattr(self, 'xor'): del self.xor
            if hasattr(self, 'tkimage'): del self.tkimage
            self._image = None
        else:
            self._image = image.copy()
            self.top = 0
            self.left = 0
            self.right = self.w
            self.bottom = self.h
            mult = len(self.image.mode)
            #self.blurred = image.copy().filter(
            #    ImageFilter.SMOOTH_MORE).point([x/2 for x in range(256)] * mult)
            self.xor = image.copy().point([x ^ 128 for x in range(256)] * mult)
            self.blurred = image.copy().filter(ImageFilter.SMOOTH).point([x/2.5 for x in range(256)] * mult)
            self.blurred = image.copy().filter(ImageFilter.SMOOTH).point([x/2.5 for x in range(256)] * mult)
        self.render()

    def fix(self, a, b, lim, round):
        a, b = sorted((b,a))
        a = clamp(a, 0, lim)
        b = clamp(b, 0, lim)
        if round: a = int(math.floor(a * 1. / self.round)*self.round)
        return a, b

    def set_crop(self, top, left, right, bottom, round=False):
        self.top, self.bottom = self.fix(top, bottom, self.h, round)
        self.left, self.right = self.fix(left, right, self.w, round)
        self.render()

    def get_image(self):
        return self._image
    image = property(get_image, set_image, None,
                "change the target of this DragManager")

    def render(self):
        if not self.render_flag:
            self.render_flag = True
            app.after_idle(self.do_render)

    def do_render(self):
        if not self.render_flag: return
        self.render_flag = False
        if self.image is None:
            self.l.configure(image=self.dummy_tkimage)
            return

        mask = Image.new('1', self.image.size, 0)
        mask.paste(1, (self.left, self.top, self.right, self.bottom))
        image = Image.composite(self.image, self.blurred, mask)

        t, l, r, b = self.top, self.left, self.right, self.bottom
        dx = (r - l) / 4
        dy = (b - t) / 4

        if self.inf:
            self.inf.configure(text=
                "Left:  %4d  Top:    %4d\n"
                "Right: %4d  Bottom: %4d\n"
                "Width: %4d  Height: %4d\n"
                "State: %s" % (l, t, r, b, r-l, b-t, self.state),
                font="fixed")

        #mask = Image.new('1', self.image.size, 1)
        #draw = ImageDraw.Draw(mask)

        #image = Image.composite(image, self.xor, mask)
        draw = ImageDraw.Draw(image)
        
        self.drawpreviousboxes(draw)
        # actual box
        draw.line([l, t, r, t], fill="#000000")
        draw.line([l, b, r, b], fill="#000000")
        draw.line([l, t, l, b], fill="#000000")
        draw.line([r, t, r, b], fill="#000000")

        # draggable areas
        draw.line([l+dx, t, l+dx, t+dy, l, t+dy], fill="#000000")
        draw.line([r-dx, t, r-dx, t+dy, r, t+dy], fill="#000000")
        draw.line([l+dx, b, l+dx, b-dy, l, b-dy], fill="#000000")
        draw.line([r-dx, b, r-dx, b-dy, r, b-dy], fill="#000000")


        #image = Image.composite(image, self.xor, mask)
        
        self.tkimage = ImageTk.PhotoImage(image)
        self.l.configure(image=self.tkimage)

    def drawpreviousboxes(self, draw):
        for (imnum, scale, box) in self.cropinfo:
            (l, t, r, b) = box
            l = l/scale
            t = t/scale
            r = r/scale
            b = b/scale
            draw.rectangle([(l,t),(r,b)], fill="#FF0000")
            draw.line([l, t, r, t], fill="#000000")
            draw.line([l, b, r, b], fill="#000000")
            draw.line([l, t, l, b], fill="#000000")
            draw.line([r, t, r, b], fill="#000000")
            
    def enter(self, event):
        pass
    def leave(self, event):
        pass

    def classify(self, x, y):
        t, l, r, b = self.top, self.left, self.right, self.bottom
        dx = (r - l) / 4
        dy = (b - t) / 4

        if x < l: return DRAG_NONE
        if x > r: return DRAG_NONE
        if y < t: return DRAG_NONE
        if y > b: return DRAG_NONE

        if x < l+dx:
            if y < t+dy: return DRAG_TL
            if y < b-dy: return DRAG_L
            return DRAG_BL
        if x < r-dx:
            if y < t+dy: return DRAG_T
            if y < b-dy: return DRAG_C
            return DRAG_B
        else:
            if y < t+dy: return DRAG_TR
            if y < b-dy: return DRAG_R
            return DRAG_BR

    def start(self, event):
        self.x0 = event.x
        self.y0 = event.y
        self.state = self.classify(event.x, event.y)

    def motion(self, event):
        dx = event.x - self.x0; self.x0 = event.x
        dy = event.y - self.y0; self.y0 = event.y
        new_top, new_left, new_right, new_bottom = \
            self.top, self.left, self.right, self.bottom
        if self.state == DRAG_C:
            # A center drag bumps into the edges
            if dx > 0:
                dx = min(dx, self.w - self.right - 1)
            else:
                dx = max(dx, -self.left)
            if dy > 0:
                dy = min(dy, self.h - self.bottom - 1)
            else:
                dy = max(dy, -self.top)
        if self.state in (DRAG_TL, DRAG_T, DRAG_TR, DRAG_C):
            new_top = self.top + dy
        if self.state in (DRAG_TL, DRAG_L, DRAG_BL, DRAG_C):
            new_left = self.left + dx
        if self.state in (DRAG_TR, DRAG_R, DRAG_BR, DRAG_C):
            new_right = self.right + dx
        if self.state in (DRAG_BL, DRAG_B, DRAG_BR, DRAG_C):
            new_bottom = self.bottom + dy
        # A drag never moves left past right and so on
        new_top = min(self.bottom, new_top)
        new_left = min(self.right, new_left)
        new_right = max(self.left, new_right)
        new_bottom = max(self.top, new_bottom)

        self.set_crop(new_top, new_left, new_right, new_bottom)

    def end(self, event):
        self.set_crop(self.top, self.left, self.right, self.bottom, False)
        self.state = DRAG_NONE

    def close(self):
        self.v.set(-1)
        
    def finish(self): # done with all slices
        self.mainloop = False
        self.v.set(1)

    def done(self): # done with 1 slice
        self.cropredolist = [] # if the user adds a crop, the redo list should be emptied
        self.v.set(1)
        #self.render()

    def double(self, event):
        self.done()

    def undocrop(self):
        try:
            titem = self.cropinfo.pop()
            self.cropredolist.append(titem)
            self.render()
        except IndexError:
            # already at first state
            print "No more crops to undo"
            

    def redocrop(self):
        try:
            titem = self.cropredolist.pop()
            self.cropinfo.append(titem)
            self.render()
        except IndexError:
            # already at latest state
            print "No more crops to redo"

    def wait(self):
        app.wait_variable(self.v)
        value = self.v.get()
        if value == -1: raise SystemExit
        return value


max_h = app.winfo_screenheight() - 64 - 32
max_w = app.winfo_screenwidth() - 64

drag = DragManager(preview, info)
app.wm_protocol('WM_DELETE_WINDOW', app.destroy)

def image_names():
    if len(sys.argv) > 1:
        for i in sys.argv[1:]: yield i
    else:
        name = tkFileDialog.askopenfilename(filetypes=[('All files','.jpg .png')])
        if name: yield name

imnum = 0
for image_name in image_names():
    while drag.mainloop:
        imnum += 1
        i = Image.open(image_name)
        imcopy = i.copy()
        iw, ih = i.size
        scale=1
        while iw > max_w or ih > max_h:
            iw /= 2
            ih /= 2
            scale *= 2
        i.thumbnail((iw, ih))
        drag.image = i
        drag.round = max(1, 8/scale)
        if not drag.wait(): continue # user hit "next" (tba) without cropping
        
        base, ext = os.path.splitext(image_name)
        t, l, r, b = drag.top, drag.left, drag.right, drag.bottom
        t *= scale
        l *= scale
        r *= scale
        b *= scale
        box = (l, t, r, b)
        part = imcopy.crop(box)
        part.save("C:\Users\Eigenaar\Projecten\BookIndexingTool\plaatje.png")
        oldw, oldh = imcopy.size
        if not (l == 0 and t == 0 and r == oldw and b == oldh):
            drag.cropinfo.append((imnum, scale, box))
            print "Added: "+str(box)
        else:
            print "(did not add "+str(box)+", too large)"

app.destroy()
